package com.tbc.soa.json;

import java.lang.reflect.Type;
import java.util.List;

/**
 * <p>
 * JSONDeserializer takes as input a json string and produces a static typed
 * object graph from that json representation. By default it uses the class
 * property in the json data in order to map the untyped generic json data into
 * a specific Java type. However, you are limited to only json strings with
 * class information embedded when resolving it into a Java type. But, for now
 * let's just look at the simplest case of class attributes in your json. We'll
 * look at how {@link JSONSerializer} and JSONDeserializer pair together out of
 * the box.
 * </p>
 * <p>
 * Say we have a simple object like Hero (see the superhero package under the
 * test and mock). To create a json represenation of Hero we'd do the following:
 * </p>
 * 
 * <pre>
 * Hero harveyBirdman = new Hero(&quot;Harvey Birdman&quot;, new SecretIdentity(
 * 		&quot;Attorney At Law&quot;), new SecretLair(&quot;Sebben &amp; Sebben&quot;));
 * String jsonHarvey = new JSONSerialize().serialize(hero);
 * </pre>
 * <p>
 * Now to reconsitute Harvey to fight for the law we'd use JSONDeserializer like
 * so:
 * </p>
 * 
 * <pre>
 * Hero hero = new JSONDeserializer&lt;Hero&gt;().deserialize(jsonHarvey);
 * </pre>
 * <p>
 * Pretty easy when all the type information is included with the JSON data. Now
 * let's look at the more difficult case of how we might reconstitute something
 * missing type info.
 * </p>
 * <p>
 * Let's exclude the class attribute in our json like so:
 * </p>
 * 
 * <pre>
 * String jsonHarvey = new JSONSerialize().exclude(&quot;*.class&quot;).serialize(hero);
 * </pre>
 * <p>
 * The big trick here is to replace that type information when we instantiate
 * the deserializer. To do that we'll use the
 * {@link com.tbc.soa.json.JSONDeserializer#use(String, Class)} method like so:
 * </p>
 * 
 * <pre>
 * Hero hero = new JSONDeserializer&lt;Hero&gt;().use(null, Hero.class).deserialize(
 * 		jsonHarvey);
 * </pre>
 * <p>
 * Like riding a horse with no saddle without our type information. So what is
 * happening here is we've registered the Hero class to the root of the json.
 * The {@link com.tbc.soa.json.JSONDeserializer#use(String, Class)} method uses the
 * object graph path to attach certain classes to those locations. So, when the
 * deserializer is deserializing it knows where it is in the object graph. It
 * uses that graph path to look up the java class it should use when
 * reconstituting the object.
 * </p>
 * <p>
 * Notice that in our json you'd see there is no type information in the stream.
 * However, all we had to do is point the class at the Hero object, and it
 * figured it out. That's because it uses the target type (in this case Hero) to
 * figure out the other types by inspecting that class. Meaning notice that we
 * didn't have to tell it about SecretLair or SecretIdentity. That's because it
 * can figure that out from the Hero class.
 * </p>
 * <p>
 * Pretty cool. Where this fails is when we starting working with interfaces,
 * abstract classes, and subclasses. Yea our friend polymorphism can be a pain
 * when deserializing. Why? Well if you haven't realized by now inspecting the
 * type from our target class won't help us because either it's not a concrete
 * class or we can't tell the subclass by looking at the super class alone. Next
 * section we're going to stand up on our bare back horse. Ready? Let's do it.
 * </p>
 * <p>
 * Before we showed how the {@link com.tbc.soa.json.JSONDeserializer#use(String, Class)}
 * method would allow us to plug in a single class for a given path. That might
 * work when you know exactly which class you want to instantiate, but when the
 * class type depends on external factors we really need a way to specify
 * several possibilities. That's where the second version of
 * {@link com.tbc.soa.json.JSONDeserializer#use(String, ClassLocator)} comes into play.
 * {@link com.tbc.soa.json.ClassLocator} allow you to use a stradegy for finding which
 * java Class you want to attach at a particular object path.
 * </p>
 * <p>
 * {@link com.tbc.soa.json.JSONDeserializer#use(String, ClassLocator)} have access to
 * the intermediate form of the object as a Map. Given the Map at the object
 * path the ClassLocator figures out which Class Flexjson will bind the
 * parameters into that object.
 * </p>
 * <p>
 * Let's take a look at how this can be done using our Hero class. All Heros
 * have a list of super powers. These super powers are things like X Ray Vision,
 * Heat Vision, Flight, etc. Each super power is represented by a subclass of
 * SuperPower. If we serialize a Hero without class information embedded we'll
 * need a way to figure out which instance to instantiate when we deserialize.
 * In this example I'm going to use a Transformer during serialization to embed
 * a special type information into the object. All this transformer does is
 * strip off the package information on the class property.
 * </p>
 * 
 * <pre>
 * String json = new JSONSerializer().include(&quot;powers.class&quot;).transform(
 * 		new SimpleTransformer(), &quot;powers.class&quot;).exclude(&quot;*.class&quot;).serialize(
 * 		superhero);
 * Hero hero = new JSONDeserializer&lt;Hero&gt;().use(&quot;powers.class&quot;,
 * 		new PackageClassLocator()).deserialize(json);
 * </pre>
 * <p>
 * 
 * </p>
 * <p>
 * All objects that pass through the deserializer must have a no argument
 * constructor. The no argument constructor does not have to be public. That
 * allows you to maintain some encapsulation. JSONDeserializer will bind
 * parameters using setter methods of the objects instantiated if available. If
 * a setter method is not available it will using reflection to set the value
 * directly into the field. You can use setter methods transform the any data
 * from json into the object structure you want. That way json structure can be
 * different from your Java object structure. The works very much in the same
 * way getters do for the {@link com.tbc.soa.json.JSONSerializer}.
 * </p>
 * <p>
 * Collections and Maps have changed the path structure in order to specify
 * concrete classes for both the Collection implementation and the contained
 * values. Normally you would use generics to specify the concrete class to
 * load. However, if you're contained class is an interface or abstract class
 * then you'll need to define those concrete classes using paths. To specify the
 * concrete class for a Collection use the path to the collection. To specify
 * the contained instance's concrete class append "values" onto the path. For
 * example, if your collection path is "person.friends" you can specify the
 * collection type using:
 * </p>
 * 
 * <pre>
 * new JSONDeserializer().use(&quot;person.friends&quot;, ArrayList.class).use(
 * 		&quot;person.friends.values&quot;, Frienemies.class)
 * </pre>
 * <p>
 * Notice that append "values" onto the "person.friends" to specify the class to
 * use inside the Collection. Maps have both keys and values within them. For
 * Maps you can specify those by appending "keys" and "values" to the path.
 * </p>
 * <p>
 * Now onto the advanced topics of the deserializer.
 * {@link com.tbc.soa.json.ObjectFactory} interface is the underpinnings of the
 * deserializer. All object creation is controlled by ObjectFactories. By
 * default there are many ObjectFactories registered to handle all of the
 * default types supported. However, you can add your own implementations to
 * handle specialized formats. For example, say you've encoded your Dates using
 * yyyy.MM.dd. If you want to read these into java.util.Date objects you can
 * register a {@link com.tbc.soa.json.transformer.DateTransformer} to deserialize dates
 * into Date objects.
 * </p>
 */
public class JSONDeserializer<T> {

	public JSONDeserializer() {
	}

	@SuppressWarnings("unchecked")
	public T deserialize(String input) {
		ObjectBinder binder = new ObjectBinder();
		return (T) binder.bind(new JSONTokener(input).nextValue());
	}

	@SuppressWarnings("unchecked")
	public Object[] deserializeArray(String input, Class<?>[] classArray) {
		ObjectBinder binder = new ObjectBinder();

		// Should be an ArrayList.
		Object nextValue = new JSONTokener(input).nextValue();
		List<Object> list = (List<Object>) nextValue;
		Object[] result = new Object[list.size()];
		for (int i = 0; i < classArray.length; i++) {
			result[i] = binder.bind(list.get(i), classArray[i]);
		}

		return result;
	}
	
//	@SuppressWarnings("unchecked")
//	public T deserialize(String input, Class<?> rootClass) {
//		ObjectBinder binder = new ObjectBinder();
//		return (T) binder.bind(new JSONTokener(input).nextValue(), rootClass);
//	}
	
	@SuppressWarnings("unchecked")
	public T deserialize(String input, Type rootClass) {
		ObjectBinder binder = new ObjectBinder();
		return (T) binder.bind(new JSONTokener(input).nextValue(), rootClass);
	}
	
	// public T deserialize( String input, Class root ) {
	// ObjectBinder binder = createObjectBinder();
	// return (T)binder.bind( new JSONTokener( input ).nextValue(), root );
	// }

	// public T deserialize( String input, T target ) {
	// ObjectBinder binder = new ObjectBinder();
	// Object inObj = new JSONTokener(input).nextValue();
	//        	
	// return (T)binder.bindIntoCollection( (Collection)inObj, target,
	// target.getClass() );
	// }
	// }

	// private ObjectBinder createObjectBinder() {
	// ObjectBinder binder = new ObjectBinder();
	// for( Class clazz : typeFactories.keySet() ) {
	// binder.use( clazz, typeFactories.get(clazz) );
	// }
	// for( Path p : pathFactories.keySet() ) {
	// binder.use( p, pathFactories.get( p ) );
	// }
	// return binder;
	// }

	// public JSONDeserializer<T> use( String path, ClassLocator locator ) {
	// pathFactories.put( Path.parse(path), new ClassLocatorObjectFactory(
	// locator ) );
	// return this;
	// }

	// public JSONDeserializer<T> use( String path, Class clazz ) {
	// return use( path, new StaticClassLocator(clazz) );
	// }

	// public JSONDeserializer<T> use( Class clazz, ObjectFactory factory ) {
	// typeFactories.put( clazz, factory );
	// return this;
	// }

	// public JSONDeserializer<T> use( String path, ObjectFactory factory ) {
	// pathFactories.put( Path.parse( path ), factory );
	// return this;
	// }

	// public JSONDeserializer<T> use(ObjectFactory factory, String... paths) {
	// for( String p : paths ) {
	// use( p, factory );
	// }
	// return this;
	// }
}
